﻿using System.Text.RegularExpressions;

using Storm.TextEditor.Parser.Objects.Collections;

namespace Storm.TextEditor.Parser.Objects
{
    /// <summary>
    /// A Pattern is a specific string or a RegEx pattern that is used by the parser.
    /// There are two types of patterns, Simple and Complex.
    /// 
    /// Simple Patterns are patterns that consists of a simple fixed string eg. "void" or "for".
    /// Complex Patterns are patterns that consists of RegEx patterns , eg. hex numbers or URLs can be described as RegEx patterns.
    /// </summary>
    public sealed class Pattern
    {
        #region Fields

        private string separators = " .,+-*^\\/()[]{}@:;'?£$#%& \t=<>";

        private string category = "";
        private string stringPattern = "";
        private string lowerStringPattern = "";

        private Regex regex = null;
        private Regex caseRegex = null;

        private PatternList parent = null;
        private char[] patternBuffer = null;

        private bool complex = false;
        private bool separator = false;
        private bool keyword = false;

        private BracketType bracketType = BracketType.None;
        private Pattern matchingBracket = null;
        private bool multilineBracket = true;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the string containing the separators of the Pattern.
        /// </summary>
        public string Separators
        {
            get { return separators; }
            set { separators = value; }
        }

        /// <summary>
        /// Gets or sets the category of the Pattern.
        /// </summary>
        public string Category
        {
            get { return category; }
            set { category = value; }
        }

        /// <summary>
        /// Gets or sets the Pattern as a string.
        /// </summary>
        public string StringPattern
        {
            get { return stringPattern; }
            set
            {
                stringPattern = value;
                lowerStringPattern = stringPattern.ToLower();
            }
        }

        /// <summary>
        /// Gets or sets the Pattern as a lower case string.
        /// </summary>
        public string LowerStringPattern
        {
            get { return lowerStringPattern; }
            set { lowerStringPattern = value; }
        }

        /// <summary>
        /// Gets or sets the parent PatternList of the Pattern.
        /// </summary>
        public PatternList Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        /// <summary>
        /// Gets or sets the Pattern's buffer.
        /// </summary>
        public char[] PatternBuffer
        {
            get { return patternBuffer; }
            set { patternBuffer = value; }
        }

        /// <summary>
        /// Gets or sets whether the Pattern is complex.
        /// </summary>
        public bool IsComplex
        {
            get { return complex; }
            set { complex = value; }
        }

        /// <summary>
        /// Gets or sets whether the Pattern is a separator.
        /// </summary>
        public bool IsSeparator
        {
            get { return separator; }
            set { separator = value; }
        }

        /// <summary>
        /// Gets or sets whether the Pattern is a keyword.
        /// </summary>
        public bool IsKeyword
        {
            get { return keyword; }
            set { keyword = value; }
        }

        /// <summary>
        /// Gets or sets the BracketType of the Pattern.
        /// </summary>
        public BracketType BracketType
        {
            get { return bracketType; }
            set { bracketType = value; }
        }

        /// <summary>
        /// Gets or sets the matching bracket of the Pattern.
        /// </summary>
        public Pattern MatchingBracket
        {
            get { return matchingBracket; }
            set { matchingBracket = value; }
        }

        /// <summary>
        /// Gets or sets whether the Pattern is a multiline bracket.
        /// </summary>
        public bool IsMultilineBracket
        {
            get { return multilineBracket; }
            set { multilineBracket = value; }
        }

        /// <summary>
        /// Gets whether the Pattern contains a separator character.
        /// </summary>
        public bool ContainsSeparator
        {
            get
            {
                for (int i = 0; i < this.StringPattern.Length; i++)
                {
                    char c = this.StringPattern[i];
                    if (this.Separators.IndexOf(c) >= 0)
                        return true;
                }

                return false;
            }
        }

        #endregion

        #region Methods

        #region Public

        /// <summary>
        /// Returns whether the given text has any separators from the given position.
        /// </summary>
        /// <param name="text">Text to search in.</param>
        /// <param name="position">Position to start at.</param>
        /// <returns>Whether the given text has any separators from the given position.</returns>
        public bool HasSeparators(string text, int position)
        {
            return (this.CharIsSeparator(text, position - 1) && this.CharIsSeparator(text, position + stringPattern.Length));
        }

        /// <summary>
        /// Returns the PatternScanResult containing the index of the given text from the given start position.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="matchCase">Whether the match should match case.</param>
        /// <param name="separators">Separators to use.</param>
        /// <returns>Found PatternScanResult.</returns>
        public PatternScanResult IndexIn(string text, int startPosition, bool matchCase, string separators)
        {
            return this.IndexIn(text, startPosition, -1, matchCase, separators);
        }

        /// <summary>
        /// Returns the PatternScanResult containing the index of the given text from the given start position.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
        /// <param name="matchCase">Whether the match should match case.</param>
        /// <param name="separators">Separators to use.</param>
        /// <returns>Found PatternScanResult.</returns>
        public PatternScanResult IndexIn(string text, int startPosition, int maxIndex, bool matchCase, string separators)
        {
            if (separators != null)
                this.Separators = separators;

            if (complex == false)
            {
                if (keyword == false)
                    return this.SimpleFind(text, startPosition, maxIndex, matchCase);
                else
                    return this.SimpleFindKeyword(text, startPosition, maxIndex, matchCase);
            }
            else
            {
                if (keyword == false)
                    return this.ComplexFind(text, startPosition, maxIndex);
                else
                    return this.ComplexFindKeyword(text, startPosition, maxIndex);
            }
        }

        #endregion

        #region Private

        /// <summary>
        /// Returns whether the character at the given position in the given text is a separator.
        /// </summary>
        /// <param name="text">Text to look in.</param>
        /// <param name="position">Position of the character.</param>
        /// <returns>Whether the character at the given position in the given text is a separator.</returns>
        private bool CharIsSeparator(string text, int position)
        {
            if (position < 0 || position >= text.Length)
                return true;

            char c = text[position];
            if (this.Separators.IndexOf(c) >= 0)
                return true;

            return false;
        }

        /// <summary>
        /// Finds the given text from the given starting position as a simple find.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
        /// <param name="matchCase">Whether the match case.</param>
        /// <returns>Found PatternScanResult.</returns>
        private PatternScanResult SimpleFind(string text, int startPosition, int maxIndex, bool matchCase)
        {
            int position = 0;
            if (matchCase == true)
                position = text.IndexOf(stringPattern, startPosition);
            else
                position = text.ToLower().IndexOf(lowerStringPattern, startPosition);

            PatternScanResult scanResult;
            if (position >= 0)
            {
                if (maxIndex > -1)
                {
                    if (position + this.StringPattern.Length <= maxIndex)
                    {
                        scanResult.Index = position;
                        scanResult.Token = text.Substring(position, this.StringPattern.Length);
                        return scanResult;
                    }
                }
                else
                {
                    scanResult.Index = position;
                    scanResult.Token = text.Substring(position, this.StringPattern.Length);
                    return scanResult;
                }
            }
            else
            {
                scanResult.Index = 0;
                scanResult.Token = "";
                return scanResult;
            }

            return new PatternScanResult();
        }

        /// <summary>
        /// Finds the given keyword text from the given starting position as a simple find.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
        /// <param name="matchCase">Whether the match case.</param>
        /// <returns>Found PatternScanResult.</returns>
        private PatternScanResult SimpleFindKeyword(string text, int startPosition, int maxIndex, bool matchCase)
        {
            PatternScanResult scanResult;
            while (true)
            {
                scanResult = this.SimpleFind(text, startPosition, maxIndex, matchCase);
                if (scanResult.Token == "")
                    return scanResult;

                if (this.CharIsSeparator(text, scanResult.Index - 1) == true &&
                    this.CharIsSeparator(text, scanResult.Index + scanResult.Token.Length) == true)
                {
                    return scanResult;
                }

                startPosition = scanResult.Index + 1;
                if (startPosition >= text.Length)
                {
                    scanResult.Token = "";
                    scanResult.Index = 0;
                    return scanResult;
                }
            }
        }

        /// <summary>
        /// Finds the given text from the given starting position as a complex find.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
        /// <returns>Found PatternScanResult.</returns>
        private PatternScanResult ComplexFind(string text, int startPosition, int maxIndex)
        {
            PatternScanResult scanResult;
            MatchCollection matchCollection = regex.Matches(text);

            int position = 0;
            string matchValue = "";

            foreach (Match match in matchCollection)
            {
                position = match.Index;
                matchValue = match.Value;
                if (position >= startPosition)
                {
                    if (maxIndex > -1)
                    {
                        if (position <= maxIndex)
                        {
                            scanResult.Index = position;
                            scanResult.Token = matchValue;
                            return scanResult;
                        }
                    }
                    else
                    {
                        scanResult.Index = position;
                        scanResult.Token = matchValue;
                        return scanResult;
                    }
                }
            }

            scanResult.Index = 0;
            scanResult.Token = "";
            return scanResult;
        }

        /// <summary>
        /// Finds the given keyword text from the given starting position as a complex find.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
        /// <returns>Found PatternScanResult.</returns>
        private PatternScanResult ComplexFindKeyword(string text, int startPosition, int maxIndex)
        {
            PatternScanResult scanResult;
            while (true)
            {
                scanResult = this.ComplexFind(text, startPosition, maxIndex);
                if (scanResult.Token == "")
                    return scanResult;

                if (this.CharIsSeparator(text, scanResult.Index - 1) == true &&
                    this.CharIsSeparator(text, scanResult.Index + scanResult.Token.Length) == true)
                {
                    return scanResult;
                }

                startPosition = scanResult.Index + 1;
                if (startPosition >= text.Length)
                {
                    scanResult.Token = "";
                    scanResult.Index = 0;
                    return scanResult;
                }
            }
        }

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of Pattern.
        /// </summary>
        /// <param name="pattern">The Pattern as a string.</param>
        /// <param name="complex">Whether the Pattern is complex.</param>
        public Pattern(string pattern, bool complex)
            : this(pattern, complex, false, false, "")
        {
        }

        /// <summary>
        /// Initializes a new instance of Pattern.
        /// </summary>
        /// <param name="pattern">The Pattern as a string.</param>
        /// <param name="complex">Whether the Pattern is complex.</param>
        /// <param name="separator">Separators the Pattern should use.</param>
        /// <param name="keyword">Whether the Pattern is a keyword.</param>
        public Pattern(string pattern, bool complex, bool separator, bool keyword)
            : this(pattern, complex, separator, keyword, "")
        {
        }

        /// <summary>
        /// Initializes a new instance of Pattern.
        /// </summary>
        /// <param name="pattern">The Pattern as a string.</param>
        /// <param name="complex">Whether the Pattern is complex.</param>
        /// <param name="separator">Separators the Pattern should use.</param>
        /// <param name="keyword">Whether the Pattern is a keyword.</param>
        /// <param name="escapeChar">The escape character of the Pattern.</param>
        public Pattern(string pattern, bool complex, bool separator, bool keyword, string escapeChar)
        {
            this.StringPattern = pattern;
            this.separator = separator;
            this.keyword = keyword;

            if (complex == true)
            {
                this.complex = true;

                regex = new Regex(StringPattern, RegexOptions.Compiled);
                caseRegex = new Regex(StringPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            }
            else
            {
                this.complex = false;
                patternBuffer = pattern.ToCharArray();
            }

            escapeChar = Regex.Escape(escapeChar);
            string EscPattern = string.Format("(?<=((?<!{0})({0}{0})*))({1})", escapeChar, pattern);
        }
    }
}
